{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Batched Graph Classification with DGL\n",
    "\n",
    "Graph classification is an important problem with applications across many fields – bioinformatics, chemoinformatics, social network analysis, urban computing and cyber-security. Applying graph neural networks to this problem has been a popular approach recently ( Ying et al., 2018, Cangea et al., 2018, Knyazev et al., 2018, Bianchi et al., 2019, Liao et al., 2019, Gao et al., 2019).\n",
    "\n",
    "\n",
    "### This tutorial demonstrates:\n",
    "\n",
    "- batching multiple graphs of variable size and shape with DGL\n",
    "- training a graph neural network for a simple graph classification task\n",
    "\n",
    "\n",
    "## Simple Graph Classification Task\n",
    "\n",
    "In this tutorial, we will learn how to perform batched graph classification with dgl via a toy example of classifying 8 types of regular graphs as below:\n",
    "\n",
    "![](img/SimpleGraphClassificationTask.png)\n",
    "\n",
    "We implement a synthetic dataset data.MiniGCDataset in DGL. The dataset has 8 different types of graphs and each class has the same number of graph samples.\n",
    "我们在DGL中实现了一个合成数据集data.MiniGCDataset。 数据集有8种不同类型的图形，每个类具有相同数量的图形样本。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from dgl.data import MiniGCDataset\n",
    "import matplotlib.pyplot as plt\n",
    "import networkx as nx\n",
    "# A dataset with 80 samples, each graph is\n",
    "# of size [10, 20]\n",
    "dataset = MiniGCDataset(80, 10, 20)\n",
    "graph, label = dataset[0]\n",
    "fig, ax = plt.subplots()\n",
    "nx.draw(graph.to_networkx(), ax=ax)\n",
    "ax.set_title('Class: {:d}'.format(label))\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "graph, label = dataset[10]\n",
    "fig, ax = plt.subplots()\n",
    "nx.draw(graph.to_networkx(), ax=ax)\n",
    "ax.set_title('Class: {:d}'.format(label))\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Form a graph mini-batch\n",
    "\n",
    "To train neural networks more efficiently, a common practice is to batch multiple samples together to form a mini-batch. 为了更有效地训练神经网络，通常的做法是将多个样本一起批处理以形成小批量。\n",
    "\n",
    "Batching fixed-shaped tensor inputs is quite easy (for example, batching two images of size 28×28 gives a tensor of shape 2×28×28). 批量固定形状的张量输入非常容易（例如，批量处理两个尺寸为28×28的图像会产生2×28×28的张量）。\n",
    "\n",
    "By contrast, batching graph inputs has two challenges:\n",
    "\n",
    "- Graphs are sparse.\n",
    "- Graphs can have various length (e.g. number of nodes and edges).\n",
    "\n",
    "To address this, DGL provides a dgl.batch() API. \n",
    "\n",
    "It leverages the trick that a batch of graphs can be viewed as a large graph that have many disjoint connected components. 它利用了一批图表可以被视为具有许多不相交的连接组件的大图形的技巧。\n",
    "\n",
    "Below is a visualization that gives the general idea:\n",
    "\n",
    "![](img/batch.png)\n",
    "\n",
    "We define the following collate function to form a mini-batch from a given list of graph and label pairs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import dgl\n",
    "\n",
    "def collate(samples):\n",
    "    # The input `samples` is a list of pairs\n",
    "    #  (graph, label).\n",
    "    graphs, labels = map(list, zip(*samples))\n",
    "    batched_graph = dgl.batch(graphs)\n",
    "    return batched_graph, torch.tensor(labels)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The return type of dgl.batch() is still a graph (similar to the fact that a batch of tensors is still a tensor). This means that any code that works for one graph immediately works for a batch of graphs. More importantly, since DGL processes messages on all nodes and edges in parallel, this greatly improves efficiency."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Graph Classifier\n",
    "\n",
    "The graph classification can be proceeded as follows:\n",
    "\n",
    "![](img/GraphClassifier.png)\n",
    "\n",
    "From a batch of graphs, we first perform message passing/graph convolution for nodes to “communicate” with others. After message passing, we compute a tensor for graph representation from node (and edge) attributes. This step may be called “readout/aggregation” interchangeably. Finally, the graph representations can be fed into a classifier g to predict the graph labels.\n",
    "从一批图中，我们首先对节点执行消息传递/图卷积以与其他节点“通信”。在消息传递之后，我们从节点（和边缘）属性计算图表示的张量。该步骤可以互换地称为“读出/聚合”。最后，可以将图表示馈入分类器g以预测图标签。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Graph Convolution\n",
    "\n",
    "Our graph convolution operation is basically the same as that for GCN (checkout our tutorial). The only difference is that we replace \n",
    "\n",
    "$h_{v}^{(l+1)}=\\operatorname{ReLU}\\left(b^{(l)}+\\sum_{u \\in \\mathcal{N}(v)} h_{u}^{(l)} W^{(l)}\\right)$ by\n",
    "$h_{v}^{(l+1)}=\\operatorname{ReLU}\\left(b^{(l)}+\\frac{1}{|\\mathcal{N}(v)|} \\sum_{u \\in \\mathcal{N}(v)} h_{u}^{(l)} W^{(l)}\\right)$\n",
    "\n",
    "The replacement of summation by average is to balance nodes with different degrees, which gives a better performance for this experiment.\n",
    "\n",
    "Note that the self edges added in the dataset initialization allows us to include the original node feature $h_{v}^{(l)}$ when taking the average."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "import dgl.function as fn\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "\n",
    "\n",
    "# Sends a message of node feature h.\n",
    "msg = fn.copy_src(src='h', out='m')\n",
    "\n",
    "def reduce(nodes):\n",
    "    \"\"\"Take an average over all neighbor node features hu and use it to\n",
    "    overwrite the original node feature.\"\"\"\n",
    "    accum = torch.mean(nodes.mailbox['m'], 1)\n",
    "    return {'h': accum}\n",
    "\n",
    "class NodeApplyModule(nn.Module):\n",
    "    \"\"\"Update the node feature hv with ReLU(Whv+b).\"\"\"\n",
    "    def __init__(self, in_feats, out_feats, activation):\n",
    "        super(NodeApplyModule, self).__init__()\n",
    "        self.linear = nn.Linear(in_feats, out_feats)\n",
    "        self.activation = activation\n",
    "\n",
    "    def forward(self, node):\n",
    "        h = self.linear(node.data['h'])\n",
    "        h = self.activation(h)\n",
    "        return {'h' : h}\n",
    "\n",
    "class GCN(nn.Module):\n",
    "    def __init__(self, in_feats, out_feats, activation):\n",
    "        super(GCN, self).__init__()\n",
    "        self.apply_mod = NodeApplyModule(in_feats, out_feats, activation)\n",
    "\n",
    "    def forward(self, g, feature):\n",
    "        # Initialize the node features with h.\n",
    "        g.ndata['h'] = feature\n",
    "        g.update_all(msg, reduce)\n",
    "        g.apply_nodes(func=self.apply_mod)\n",
    "        return g.ndata.pop('h')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Readout and Classification\n",
    "\n",
    "For this demonstration, we consider initial node features to be their degrees. After two rounds of graph convolution, we perform a graph readout by averaging over all node features for each graph in the batch\n",
    "\n",
    "$h_{g}=\\frac{1}{|\\mathcal{V}|} \\sum_{v \\in \\mathcal{V}} h_{v}$\n",
    "\n",
    "In DGL, dgl.mean_nodes() handles this task for a batch of graphs with variable size. We then feed our graph representations into a classifier with one linear layer to obtain pre-softmax logits."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch.nn.functional as F\n",
    "\n",
    "\n",
    "class Classifier(nn.Module):\n",
    "    def __init__(self, in_dim, hidden_dim, n_classes):\n",
    "        super(Classifier, self).__init__()\n",
    "\n",
    "        self.layers = nn.ModuleList([\n",
    "            GCN(in_dim, hidden_dim, F.relu),\n",
    "            GCN(hidden_dim, hidden_dim, F.relu)])\n",
    "        self.classify = nn.Linear(hidden_dim, n_classes)\n",
    "\n",
    "    def forward(self, g):\n",
    "        # For undirected graphs, in_degree is the same as\n",
    "        # out_degree.\n",
    "        h = g.in_degrees().view(-1, 1).float()\n",
    "        for conv in self.layers:\n",
    "            h = conv(g, h)\n",
    "        g.ndata['h'] = h\n",
    "        hg = dgl.mean_nodes(g, 'h')\n",
    "        return self.classify(hg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup and Training\n",
    "\n",
    "We create a synthetic dataset of 400 graphs with 10 ~ 20 nodes. 320 graphs constitute a training set and 80 graphs constitute a test set."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "D:\\progrom\\python\\python\\python3\\lib\\site-packages\\dgl\\base.py:18: UserWarning: Initializer is not set. Use zero initializer instead. To suppress this warning, use `set_initializer` to explicitly specify which initializer to use.\n",
      "  warnings.warn(msg)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 0, loss 2.1045\n",
      "Epoch 1, loss 1.9521\n",
      "Epoch 2, loss 1.8407\n",
      "Epoch 3, loss 1.7947\n",
      "Epoch 4, loss 1.6927\n"
     ]
    },
    {
     "ename": "KeyboardInterrupt",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-6-53b17a9d5d5d>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m     23\u001b[0m         \u001b[0mloss\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mloss_func\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mprediction\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mlabel\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     24\u001b[0m         \u001b[0moptimizer\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mzero_grad\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 25\u001b[1;33m         \u001b[0mloss\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mbackward\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     26\u001b[0m         \u001b[0moptimizer\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     27\u001b[0m         \u001b[0mepoch_loss\u001b[0m \u001b[1;33m+=\u001b[0m \u001b[0mloss\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdetach\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mitem\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\progrom\\python\\python\\python3\\lib\\site-packages\\torch\\tensor.py\u001b[0m in \u001b[0;36mbackward\u001b[1;34m(self, gradient, retain_graph, create_graph)\u001b[0m\n\u001b[0;32m     91\u001b[0m                 \u001b[0mproducts\u001b[0m\u001b[1;33m.\u001b[0m \u001b[0mDefaults\u001b[0m \u001b[0mto\u001b[0m\u001b[0;31m \u001b[0m\u001b[0;31m`\u001b[0m\u001b[0;31m`\u001b[0m\u001b[1;32mFalse\u001b[0m\u001b[0;31m`\u001b[0m\u001b[0;31m`\u001b[0m\u001b[1;33m.\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     92\u001b[0m         \"\"\"\n\u001b[1;32m---> 93\u001b[1;33m         \u001b[0mtorch\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mautograd\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mbackward\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mgradient\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mretain_graph\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcreate_graph\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     94\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     95\u001b[0m     \u001b[1;32mdef\u001b[0m \u001b[0mregister_hook\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mhook\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\progrom\\python\\python\\python3\\lib\\site-packages\\torch\\autograd\\__init__.py\u001b[0m in \u001b[0;36mbackward\u001b[1;34m(tensors, grad_tensors, retain_graph, create_graph, grad_variables)\u001b[0m\n\u001b[0;32m     88\u001b[0m     Variable._execution_engine.run_backward(\n\u001b[0;32m     89\u001b[0m         \u001b[0mtensors\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mgrad_tensors\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mretain_graph\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcreate_graph\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 90\u001b[1;33m         allow_unreachable=True)  # allow_unreachable flag\n\u001b[0m\u001b[0;32m     91\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     92\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m: "
     ]
    }
   ],
   "source": [
    "import torch.optim as optim\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "# Create training and test sets.\n",
    "trainset = MiniGCDataset(320, 10, 20)\n",
    "testset = MiniGCDataset(80, 10, 20)\n",
    "# Use PyTorch's DataLoader and the collate function\n",
    "# defined before.\n",
    "data_loader = DataLoader(trainset, batch_size=32, shuffle=True,\n",
    "                         collate_fn=collate)\n",
    "\n",
    "# Create model\n",
    "model = Classifier(1, 256, trainset.num_classes)\n",
    "loss_func = nn.CrossEntropyLoss()\n",
    "optimizer = optim.Adam(model.parameters(), lr=0.001)\n",
    "model.train()\n",
    "\n",
    "epoch_losses = []\n",
    "for epoch in range(80):\n",
    "    epoch_loss = 0\n",
    "    for iter, (bg, label) in enumerate(data_loader):\n",
    "        prediction = model(bg)\n",
    "        loss = loss_func(prediction, label)\n",
    "        optimizer.zero_grad()\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        epoch_loss += loss.detach().item()\n",
    "    epoch_loss /= (iter + 1)\n",
    "    print('Epoch {}, loss {:.4f}'.format(epoch, epoch_loss))\n",
    "    epoch_losses.append(epoch_loss)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.title('cross entropy averaged over minibatches')\n",
    "plt.plot(epoch_losses)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.eval()\n",
    "# Convert a list of tuples to two lists\n",
    "test_X, test_Y = map(list, zip(*testset))\n",
    "test_bg = dgl.batch(test_X)\n",
    "test_Y = torch.tensor(test_Y).float().view(-1, 1)\n",
    "probs_Y = torch.softmax(model(test_bg), 1)\n",
    "sampled_Y = torch.multinomial(probs_Y, 1)\n",
    "argmax_Y = torch.max(probs_Y, 1)[1].view(-1, 1)\n",
    "print('Accuracy of sampled predictions on the test set: {:.4f}%'.format(\n",
    "    (test_Y == sampled_Y.float()).sum().item() / len(test_Y) * 100))\n",
    "print('Accuracy of argmax predictions on the test set: {:4f}%'.format(\n",
    "    (test_Y == argmax_Y.float()).sum().item() / len(test_Y) * 100))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
